package com.ymt.bpm.engine.handler.activity.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.ymt.bpm.appboot.BpmpProperties;
import com.ymt.bpm.appboot.SpringContext;
import com.ymt.bpm.dao.MDao;
import com.ymt.bpm.dao.Mapper;
import com.ymt.bpm.engine.EngineConst;
import com.ymt.bpm.engine.bpmn.ProcessUtil;
import com.ymt.bpm.engine.handler.activity.AsyncServiceTaskDelegate;
import com.ymt.bpm.engine.listener.engine.exception.EngineException;
import com.ymt.bpm.engine.listener.engine.exception.ErrorCode;
import com.ymt.bpm.model.*;
import com.ymt.bpm.service.engine.bpd.BpdVersionCacheService;
import com.ymt.bpm.service.platform.log.SysLogService;
import com.ymt.bpm.util.Const;
import com.ymt.bpm.util.DateUtil;
import com.ymt.bpm.service.platform.helper.JerseyRestClient;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.runtime.Execution;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Johnny on 2016/12/24.
 */
@Component
@EnableConfigurationProperties(BpmpProperties.class)
public class AsyncServiceTaskDelegateImpl implements AsyncServiceTaskDelegate {

    public static final String SERVICE_TYPE_SERVICECALL = "servicecall";
    public static final String SERVICE_TYPE_NEWPROCESS = "newprocess";

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private BpmpProperties bpmpProperties;

    @Autowired
    private MDao dao;

    @Autowired
    private BpdVersionCacheService bvcs;

    @Autowired
    private RuntimeService runtimeService;

    @Override
    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public void execute(DelegateExecution execution) {
        if (log.isDebugEnabled()) {
            log.debug("Comming into AsyncServiceTaskDelegate...");
        }
        if ("start".equalsIgnoreCase(execution.getEventName())) {
            start(execution);
        } else if ("end".equalsIgnoreCase(execution.getEventName())) {
            end(execution);
        }

    }

    @Transactional(EngineConst.TRANSACTION_MANAGER)
    private void start(DelegateExecution execution) {
        if (log.isDebugEnabled()) {
            log.debug("AsyncServiceTaskDelegate receiving start event.");
        }
        //tenantId
        String tenantId = "".equals(execution.getTenantId()) ? null : execution.getTenantId();
        String piEid = execution.getProcessInstanceId();

        try {
            JSONObject processVar = (JSONObject) execution.getVariable(EngineConst.PROCESSVAR);

            PPi pi = new PPi();
            if (tenantId!=null && StringUtils.isNumeric(tenantId)) {
                pi.setTenantId(Integer.parseInt(tenantId));
            }
            pi.setPiEid(piEid);
            pi = dao.selectOneByObject(Mapper.PPiMapper, "selectByObj", pi);

            //config
            JSONObject version = bvcs.getVersionJson(tenantId, pi.getBpdId(), pi.getVersionid());
            JSONObject bpmnConf = version.getJSONObject(EngineConst.BPMNCONF);
            JSONObject currentActiviti = bpmnConf.getJSONObject(execution.getCurrentActivityId());

            callService(tenantId, execution, pi, execution.getCurrentActivityId(), currentActiviti, bpmnConf, processVar);
        } catch (Exception e) {
            e.printStackTrace();
            // 记录错误日志
            try {
                SysLogService logService = SpringContext.getBean(SysLogService.class);
                logService.expLog(execution.getTenantId(),
                        execution.getProcessInstanceId(),
                        execution.getCurrentActivityId(),
                        "",
                        "0",
                        null, null, e);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            throw new RuntimeException(
                    new EngineException(ErrorCode.ATVITIY_FAIL,
                            "Process instance failed on event:" + execution.getEventName()+", executionId="+execution.getId()+
                                    ", activityId="+execution.getCurrentActivityId() + ", activityName="+execution.getCurrentActivityName(),
                            e));//for rollback
        }
    }

    @Transactional(EngineConst.TRANSACTION_MANAGER)
    private void end(DelegateExecution execution) {
        if (log.isDebugEnabled()) {
            log.debug("AsyncServiceTaskDelegate receiving end event.");
        }
    }

    @Transactional(EngineConst.TRANSACTION_MANAGER)
    private void callService(String tenantId,
                             DelegateExecution execution,
                             PPi pi,
                             String activitiId,
                             JSONObject currentActiviti,
                             JSONObject bpmnConf,
                             JSONObject processVar) throws UnsupportedEncodingException {
        String svcId = JSONPath.eval(currentActiviti, "$.service.realValue").toString();
        Map<String, String> svcMap = new HashMap<String, String>();
        svcMap.put(Const.TENANT_ID, tenantId);
        svcMap.put("id", svcId);
        PService pservice = dao.selectOne(Mapper.PServiceMapper, "selectByPrimaryKey", svcMap);
        JSONObject request = new JSONObject();

        // 将流程变量映射到系统任务输入
        JSONObject params = ProcessUtil.mappingProcessVarToBizVar(activitiId, bpmnConf, processVar);

        Map<String, String> paramsMap = new HashMap<String, String>();
        paramsMap.put("_piEid", execution.getProcessInstanceId());

        //将流程变量映射到系统任务输入
        if (params!=null && !params.isEmpty()) {
            JSONObject inputs = (JSONObject)JSONPath.eval(params, "$.INPUT.children");
            for (Map.Entry<String, Object> entry : inputs.entrySet()) {
                String rawVal = entry.getValue()!=null ? ((JSONObject)entry.getValue()).getString("value") : "";
                paramsMap.put(entry.getKey(), rawVal);
            }
        }
        request.put("params", paramsMap);

        record(pi, activitiId, execution.getCurrentActivityName(), pservice, execution.getId(), request);
    }

    @Transactional(EngineConst.TRANSACTION_MANAGER)
    private void record(PPi pi, String activityId, String activityName, PService pservice, String executionId, JSONObject request) {
        PSvctaskTodoWithBLOBs st = new PSvctaskTodoWithBLOBs();
        st.setTenantId(pi.getTenantId());
        st.setPiEid(pi.getPiEid());
        st.setActivityId(activityId);
        st.setActivityName(activityName);
        st.setExecutionId(executionId);
        st.setCreateTime(DateUtil.getGMTDate());
        st.setSvcId(pservice.getId());
        st.setRetry(1);
        st.setRetryTimes(0);
        st.setStatus(PSvctaskTodo.STATUS_NEW);
        st.setSvcReq(JSONObject.toJSONString(request));
        int rs = dao.insert(Mapper.PSvctaskTodoMapper, "insert", st);
        if (log.isDebugEnabled()) {
            log.debug("inserted service call todo:"+rs);
        }
    }

    @Override
    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public void run(PSvctaskTodoWithBLOBs st) {
        if (log.isDebugEnabled()) {
            log.debug("run servicetask:"+st.getActivityId());
        }
        Map<String, String> svcMap = new HashMap<String, String>();
        if (st.getTenantId()!=null) {
            svcMap.put(Const.TENANT_ID, st.getTenantId().toString());
        }
        svcMap.put("id", st.getSvcId());
        PService pservice = dao.selectOne(Mapper.PServiceMapper, "selectByPrimaryKey", svcMap);
        JSONObject svcConfig = JSONObject.parseObject(pservice.getSvcConfig());
        JerseyRestClient client = new JerseyRestClient(svcConfig.getString("authuser"),
                svcConfig.getString("authpwd"));

        String url = svcConfig.getString("uri");
        if (!url.toLowerCase().startsWith("http")) {
            url = bpmpProperties.getSiteUrl() + url;
        }
        String tenantId = null;
        if (st.getTenantId()!=null && st.getTenantId()>0) {
            tenantId = String.valueOf(tenantId);
        }
        JSONObject request = JSONObject.parseObject(st.getSvcReq());
        JSONObject params = request.getJSONObject("params");
        Map<String, String> paramsMap = new HashMap<String, String>();
        for (String key : params.keySet()) {
            paramsMap.put(key, params.getString(key));
        }

        try {

            String resp = null;
            if ("GET".equalsIgnoreCase(svcConfig.getString("method"))) {
                resp = client.get(url, paramsMap);
            } else if ("POST".equalsIgnoreCase(svcConfig.getString("method"))) {
                resp = client.post(url, paramsMap);
            }
            log.debug("call rest service "+st.getSvcId()+" returns:"+resp);

            // 将系统任务输出映射到流程变量
            JSONObject bizOutput = JSONObject.parseObject(resp);

            Execution execution = runtimeService.createExecutionQuery().executionId(st.getExecutionId()).singleResult();
            if (bizOutput instanceof JSONObject) {
                PPi pi = new PPi();
                if (tenantId!=null && StringUtils.isNumeric(tenantId)) {
                    pi.setTenantId(Integer.parseInt(tenantId));
                }
                pi.setPiEid(st.getPiEid());
                pi = dao.selectOneByObject(Mapper.PPiMapper, "selectByObj", pi);

                //config
                JSONObject version = bvcs.getVersionJson(tenantId, pi.getBpdId(), pi.getVersionid());
                JSONObject bpmnConf = version.getJSONObject(EngineConst.BPMNCONF);
                JSONObject processVar = (JSONObject)runtimeService.getVariable(st.getExecutionId(), EngineConst.PROCESSVAR);
                ProcessUtil.mappingBizVarToProcessVar(st.getActivityId(), bpmnConf, processVar, bizOutput, false);
                runtimeService.setVariable(st.getExecutionId(), EngineConst.PROCESSVAR, processVar);
                log.debug("got processVar=" + processVar);
            }

            BeanCopier copier = BeanCopier.create(PSvctaskTodoWithBLOBs.class, PSvctaskDoneWithBLOBs.class, false);
            PSvctaskDoneWithBLOBs done = new PSvctaskDoneWithBLOBs();
            copier.copy(st, done, null);
            done.setSvcResp(resp);
            done.setStatus(PSvctaskTodoWithBLOBs.STATUS_FINISH);
            done.setLastCallTime(DateUtil.getGMTDate());
            dao.delete(Mapper.PSvctaskTodoMapper, "deleteByPrimaryKey", st);
            dao.insert(Mapper.PSvctaskDoneMapper, "insert", done);

            //trigger execution to continue
            runtimeService.signal(execution.getId());

        } catch (Exception e) {
            st.setStatus(PSvctaskTodoWithBLOBs.STATUS_FAIL);
            int retry = st.getRetry()!=null && st.getRetry()>0 ? st.getRetry() : 1;
            if (st.getRetryTimes()!=null && st.getRetryTimes()>0) {
                st.setRetryTimes(st.getRetryTimes()+1);
            } else {
                st.setRetryTimes(1);
            }
            st.setLastCallTime(DateUtil.getGMTDate());
            dao.update(Mapper.PSvctaskTodoMapper, "updateByPrimaryKeySelective", st);

            e.printStackTrace();
            log.warn("call service failed with exception:"+e.getMessage());
            // 记录错误日志
            try {
                SysLogService logService = SpringContext.getBean(SysLogService.class);
                logService.expLog(tenantId,
                        st.getPiEid(),
                        st.getActivityId(),
                        "",
                        "0",
                        null, null, e);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            throw new RuntimeException(
                    new EngineException(ErrorCode.ATVITIY_FAIL,
                            "Process instance failed on service call:" + st.getId()+", piEid="+st.getPiEid()+
                                    ", activityId="+st.getActivityId(),
                            e));//for rollback
        }
    }

}
